﻿using Google.Protobuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Cemit.PolyProto;
using Cemit.Awaiter;
using Newtonsoft.Json;

namespace Cemit.NetFramework
{
    public delegate void MessagesListener(IMessage message);
    public delegate void LogFun(string log, int line);

    public struct DynamicReport
    {
        public AsyncReport asyncReport;
        public dynamic message;
    }

    /// <summary>
    /// PolyNetwork封装的Socket，封装了连接、发送、消息监听、接受并进行拆解和分发消息
    /// </summary>
    /// 
    public partial class PolySocket
    {
        public Socket socket;
        public Queue<ByteArray> writeQueue;
        public ByteArray readBuff;
        public Queue<IMessage> messagesQueue;
        public int messagesQueueCount = 0;
        public float lastPingTime;
        public float lastPongTime;

        public bool IsConnecting { get; private set; }
        public bool IsConnected => socket != null && socket.Connected;

        private event Action OnConnectionSucceeded;
        private event Action<string> OnConnectionFailed;

        private readonly Dictionary<Type, MessagesListener> messagesListenersPairs;

        private ReceiveCallbacks m_ReceiveCallbacks = new ReceiveCallbacks();
        private Logger m_Logger;

        private bool m_IsClosing;

        private Pool<AsyncAction<DynamicReport>> m_ReportPool = new Pool<AsyncAction<DynamicReport>>();
        private Dictionary<string, Action<DynamicReport>> m_DynamicMessageListeners = new Dictionary<string, Action<DynamicReport>>();

        public PolySocket()
        {
            //初始化为TCP连接
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //接收缓冲区
            readBuff = new ByteArray();
            //写入队列
            writeQueue = new Queue<ByteArray>();

            messagesQueue = new Queue<IMessage>();

            lastPingTime = Time.time;
            lastPongTime = Time.time;

            socket.NoDelay = true;

            messagesListenersPairs = new Dictionary<Type, MessagesListener>();

            AddMessageListener<Msg_Net_Confirm>(OnReceiveConfirmMsg);
            AddMessageListener<D>(OnReceiveDMsg);
        }

        private void OnReceiveDMsg(IMessage message)
        {
            D msg = message as D;

            if (msg.Index == 0)
            {
          
                m_DynamicMessageListeners[msg.Key]?.Invoke(new DynamicReport 
                {
                    asyncReport = new AsyncReport(true),
                    message = JsonConvert.DeserializeObject<dynamic>(msg.Data).D
                });
                return;
            }

            var report = m_ReportPool.Pop(msg.Index);
            report.ReportResult(new DynamicReport
            {
                asyncReport = new AsyncReport(true),
                message = JsonConvert.DeserializeObject<dynamic>(msg.Data).D
            });
        }

        /// <summary>
        /// 设置Logger
        /// </summary>
        public PolySocket SetLogger(Action<string> log, Action<string> logWarning, Action<string> logError)
        {
            m_Logger = new Logger(log, logWarning, logError);
            return this;
        }

        /// <summary>
        /// 设置Logger
        /// </summary>
        public PolySocket SetLogger(LogFun log, LogFun logWarning, LogFun logError)
        {
            m_Logger = new Logger(log, logWarning, logError);
            return this;
        }

        /// <summary>连接到目标服务器</summary>
        public MonoAsyncAction<AsyncReport> Connect(string ip, int port)
        {
            MonoAsyncAction<AsyncReport> action = new MonoAsyncAction<AsyncReport>();

            Connect(ip, port, 
                () => action.ReportResult(new AsyncReport(true)), 
                output => action.ReportResult(new AsyncReport(false, output)));

            return action;
        }

        /// <summary>
        /// 连接到目标服务器
        /// </summary>
        public PolySocket Connect(string ip, int port, Action onConnectionSucceeded = null, 
            Action<string> onConnectionFailed = null)
        {
            if (IsConnected || IsConnecting)
            {
                m_Logger.LogError("服务器已连接或正在进行连接！");
                return this;
            }

            m_Logger.Log($"尝试连接: {ip}:{port}");

            StartReceive();

            IsConnecting = true;
            OnConnectionSucceeded += onConnectionSucceeded;
            OnConnectionFailed += onConnectionFailed;
            socket.BeginConnect(ip, port, ConnectCallback, socket);
            return this;
        }

        //TODO: 封装连接
        private void ConnectCallback(IAsyncResult ar)
        {
            try
            {
                Socket socket = (Socket)ar.AsyncState;
                socket.EndConnect(ar);
                IsConnecting = false;
                OnConnectionSucceeded?.Invoke();
                m_Logger.Log("服务器已连接！");
            }
            catch (Exception e)
            {
                IsConnecting = false;
                OnConnectionFailed?.Invoke(e.Message);
                m_Logger.LogError($"服务器连接失败: {e.Message}");
            }
        }

        public MonoAsyncAction<DynamicReport> Send(string key, dynamic message, bool unRequireReport = false)
        {
            var action = new MonoAsyncAction<DynamicReport>();
            int index = unRequireReport ? 0 : m_ReportPool.Push(action);
            Send(new D
            {
                Key = key,
                Data = JsonConvert.SerializeObject(new { D = message }),
                Index = index
            });
            return action;
        }

        public ReceiveCallbacks Send(IMessage message)
        {
            if (!IsConnected)
            { 
                m_Logger.LogWarning("消息发送失败：服务器未连接！");
                return m_ReceiveCallbacks;
            }

            if (!(message is Msg_Net_Ping))
            {
                m_Logger.Log("发送消息：" + message.GetType().Name);
            }

            byte[] nameBytes = PolyProto.MessageEncoding.EncodeName(message);
            byte[] bodyBytes = PolyProto.MessageEncoding.Encode(message);

            int length = nameBytes.Length + bodyBytes.Length;
            byte[] sendBytes = new byte[2 + length];

            sendBytes[0] = (byte)(length % 256);
            sendBytes[1] = (byte)(length / 256);

            Array.Copy(nameBytes, 0, sendBytes, 2, nameBytes.Length);
            Array.Copy(bodyBytes, 0, sendBytes, 2 + nameBytes.Length, bodyBytes.Length);

            ByteArray byteArray = new ByteArray(sendBytes);
            int count = 0;
            lock (writeQueue)
            {
                writeQueue.Enqueue(byteArray);
                count = writeQueue.Count;
            }
            if (count == 1)
            {
                socket.BeginSend(sendBytes, 0, sendBytes.Length, 0, SendCallback, socket);
            }

            return m_ReceiveCallbacks;
        }

        private void SendCallback(IAsyncResult ar)
        {
            if (!(ar.AsyncState is Socket socket) || !socket.Connected)
            {
                Debug.LogError("发送数据失败：服务器已断开！");
                return;
            }

            int count = socket.EndSend(ar); //获取成功发送的字节个数
            Debug.Log("已发送数据：" + count + " bytes");

            bool sendFlag = true;
            ByteArray byteArray;
            lock (writeQueue)
            {
                byteArray = writeQueue.First();
            }

            byteArray.readIndex += count;
            if (byteArray.Length == 0) //全部数据已发送
            {
                lock (writeQueue)
                {
                    writeQueue.Dequeue();
                }
                if (writeQueue.Count > 0)
                {
                    byteArray = writeQueue.First();
                }
                else
                {
                    sendFlag = false;
                }
            }

            if (sendFlag)
            {
                socket.BeginSend(byteArray.bytes, byteArray.readIndex, byteArray.Length, 0, SendCallback, socket);
            }
            else if (m_IsClosing)
            {
                socket.Close();
            }
        }

        /// <summary>
        /// 开始接受信息，如果服务器已经连接的话则立刻开始接受信息，否则委托到服务器连接完毕时开始接收
        /// </summary>
        /// <returns></returns>
        private PolySocket StartReceive()
        {
            //服务器还未连接
            if (IsConnecting || !IsConnected)
            {
                OnConnectionSucceeded += BeginReceive;
                return this;
            }

            BeginReceive();

            return this;

            void BeginReceive()
                => socket.BeginReceive(
                    readBuff.bytes, readBuff.writeIndex, readBuff.Remain, 0, ReceiveCallback, socket);
        }

        private void ReceiveCallback(IAsyncResult ar)
        {
            //try
            //{
                Socket socket = (Socket)ar.AsyncState;
                int count = socket.EndReceive(ar);
                if (count == 0)
                {
                    Close();
                    return;
                }

                readBuff.writeIndex += count;

                ReceiveData();

                if (readBuff.Remain < 64)
                {
                    readBuff.MoveBytes();
                    readBuff.ReSize(readBuff.Length * 2);
                }

                socket.BeginReceive(
                    readBuff.bytes, readBuff.readIndex, readBuff.Remain, 0, ReceiveCallback, socket);
            //}
            //catch (Exception e)
            //{
            //    m_Logger.LogWarning("接收数据失败：" + e.Message);
                
            //    if (IsConnected)
            //    {
            //        socket.BeginReceive(
            //        readBuff.bytes, readBuff.readIndex, readBuff.Remain, 0, ReceiveCallback, socket);
            //    }
            //}

            //解析消息，然后加入messagesQueue队列中
            void ReceiveData()
            {
                //readBuff: 0904namehello
                int msgLength = readBuff.ReadInt16();
                if (msgLength == 0 || readBuff.Length < msgLength)
                {
                    return; //处理分包
                }
                readBuff.readIndex += 2;
                //readBuff: 04namehello

                string protoName = PolyProto.MessageEncoding.DecodeName(
                    readBuff.bytes, readBuff.readIndex, out int nameCount);
                if (string.IsNullOrEmpty(protoName))
                {
                    m_Logger.LogError("接收到错误的数据：消息名为空！");
                    return;
                }
                readBuff.readIndex += nameCount;
                //readBuff: hello

                int bodyCount = msgLength - nameCount;
                byte[] bodyBytes = new byte[bodyCount];
                Array.Copy(readBuff.bytes, readBuff.readIndex, bodyBytes, 0, bodyCount);

                IMessage message = PolyProto.MessageEncoding.Decode(protoName, bodyBytes);
                if (!(message is Msg_Net_Pong))
                {
                    m_Logger.Log($"已接收消息：{msgLength} bytes - {message.GetType().Name}");
                }

                readBuff.readIndex += bodyCount;
                readBuff.CheckAndMoveBytes();

                lock (messagesQueue)
                {
                    messagesQueue.Enqueue(message);
                    messagesQueueCount++;
                }
                if (readBuff.Length > 2)
                {
                    ReceiveData();
                }
            }
        }

        /// <summary>
        /// 分发消息
        /// </summary>
        public PolySocket FireMessage(int count)
        {
            if (messagesQueueCount == 0)
            {
                return this;
            }

            for (int i = 0; i < count; i++)
            {
                IMessage message = null;
                lock (messagesQueue)
                {
                    if (messagesQueue.Count > 0)
                    {
                        message = messagesQueue.Dequeue();
                        messagesQueueCount--;
                    }
                }
                if (message != null)
                {
                    FireMessage(message);
                }
                else
                {
                    break;
                }
            }

            return this;
        }

        /// <summary>
        /// 分发消息
        /// </summary>
        private void FireMessage(IMessage message)
        {
            if (messagesListenersPairs.ContainsKey(message.GetType()))
            {
                messagesListenersPairs[message.GetType()].Invoke(message);
            }

            m_ReceiveCallbacks.Invoke(message);
        }

        /// <summary>
        /// 关闭连接
        /// </summary>
        public bool Close()
        {
            if (socket == null || !socket.Connected)
            {
                m_Logger.LogError("关闭服务器失败：服务器已断开或未连接。");
                return false;
            }
            if (IsConnecting)
            {
                m_Logger.LogError("关闭服务器失败：服务器正在连接中。");
                return false;
            }
            if (writeQueue.Count > 0)
            {
                //延迟到写入队列发送完成后关闭
                m_IsClosing = true;
            }
            else
            {
                socket.Close();
            }
            return true;
        }

        public PolySocket AddDynamicMessageListener(string key, Action<DynamicReport> action)
        {
            if (!m_DynamicMessageListeners.ContainsKey(key))
            {
                m_DynamicMessageListeners.Add(key, action);
            }
            else
            {
                m_DynamicMessageListeners[key] += action;
            }
            return this;
        }

        /// <summary>
        /// 为消息添加一个监听
        /// </summary>
        public PolySocket AddMessageListener<T>(MessagesListener listener) where T : IMessage
        {
            if (messagesListenersPairs.ContainsKey(typeof(T)))
            {
                messagesListenersPairs[typeof(T)] += listener;
            }
            else
            {
                messagesListenersPairs[typeof(T)] = listener;
            }
            return this;
        }

        /// <summary>
        /// 为消息设置一个监听，替换掉原本的所有监听
        /// </summary>
        public PolySocket SetMessageListener<T>(MessagesListener listener) where T : IMessage
        {
            messagesListenersPairs[typeof(T)] = listener;
            return this;
        }

        /// <summary>
        /// 删除某个消息监听
        /// </summary>
        public PolySocket RemoveMessageListener<T>(MessagesListener listener) where T : IMessage
        {
            if (messagesListenersPairs.ContainsKey(typeof(T)))
            {
                messagesListenersPairs[typeof(T)] -= listener;
                if (messagesListenersPairs[typeof(T)] == null)
                {
                    messagesListenersPairs.Remove(typeof(T));
                }
            }
            return this;
        }

        private static void OnReceiveConfirmMsg(IMessage message)
        {

        }

        /// <summary>
        /// 提供对Log相关委托的快速调用（内部已进行了空引用判断）
        /// </summary>
        struct Logger
        {
            private readonly LogFun m_Log;
            private readonly LogFun m_LogWarning;
            private readonly LogFun m_LogError;

            public Logger(Action<string> log, Action<string> logWarning, Action<string> logError)
            {
                m_Log = (s, l) => log(s);
                m_LogWarning = (s, l) => logWarning(s);
                m_LogError = (s, l) => logError(s);
            }

            public Logger(LogFun log, LogFun logWarning, LogFun logError)
            {
                m_Log = log;
                m_LogWarning = logWarning;
                m_LogError = logError;
            }

            public void Log(string v, [CallerLineNumber] int line = 0)
            {
                m_Log?.Invoke(v, line);
            }

            public void LogWarning(string v, [CallerLineNumber] int line = 0)
            {
                m_LogWarning?.Invoke(v, line);
            }

            public void LogError(string v, [CallerLineNumber] int line = 0)
            {
                m_LogError?.Invoke(v, line);
            }
        }

        /// <summary>
        /// 管理消息到达时的回调
        /// </summary>
        public class ReceiveCallbacks
        {
            private readonly Dictionary<Type, Action<IMessage>> receiveCallbacks
                 = new Dictionary<Type, Action<IMessage>>();

            private readonly Dictionary<string, Action> confirmCallbacks
                 = new Dictionary<string, Action>();

            // 使用SendConfirm和CallbackSetConfirmCallback设置没有消息内容的回调
            // LANQ 2019.9.27
            /// <summary>
            /// 为某个消息添加一个回调
            /// </summary>
            //public ReceiveCallbacks AddCallback<T>(Action callback) where T : IMessage
            //{
            //    AddCallback<T>((m) => callback?.Invoke());
            //    return this;
            //}

            /// <summary>
            /// 为某个消息添加一个回调
            /// </summary>
            public ReceiveCallbacks AddCallback<T>(Action<T> callback) where T : IMessage
            {
                if (!receiveCallbacks.ContainsKey(typeof(T)))
                {
                    receiveCallbacks.Add(typeof(T), (m) => callback?.Invoke((T)m));
                    return this;
                }

                receiveCallbacks[typeof(T)] += (m) => callback?.Invoke((T)m);
                return this;
            }

            /// <summary>
            /// 为Comfirm消息添加一个回调
            /// </summary>
            public void SetConfirmCallback(string tag, Action callback)
            {
                if (!confirmCallbacks.ContainsKey(tag))
                {
                    confirmCallbacks.Add(tag, callback);
                    return;
                }

                confirmCallbacks[tag] = callback;
            }

            /// <summary>
            /// 使用消息激活某个回调
            /// </summary>
            public void Invoke(IMessage message)
            {
                //处理Confirm消息
                if (message is Msg_Net_Confirm confirm)
                {
                    if (!confirmCallbacks.ContainsKey(confirm.Tag))
                    {
                        return;
                    }

                    confirmCallbacks[confirm.Tag]?.Invoke();
                    return;
                }

                if (!receiveCallbacks.ContainsKey(message.GetType()))
                {
                    return;
                }

                receiveCallbacks[GetMsgType()]?.Invoke(message);
                receiveCallbacks[GetMsgType()] = null;

                Type GetMsgType()
                    => message.GetType();
            }
        }
    }
}
